#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import json
import time
from typing import List

from oracle import logger
from oracle.common.device_manager.storage_base import StorageBase
from oracle.common.device_manager.https_request import HttpRequest
from oracle.common.device_manager.constants import ReturnCode, AssociateObjectType
from oracle.common.device_manager.device_info import LunInfo, StorageTaskInfo, ProtectGroupInfo, \
    SnapshotConsistencyGroup, SnapshotInfo, DeviceSystemInfo, HyperMetroPair, StoragePoolInfo


class StorageCommon(StorageBase):
    def __init__(self, device_info):
        super().__init__(device_info)

    def query_lun_in_protect_group(self, protect_group_id, lun_infos: List):
        req = HttpRequest()
        req.method = 'GET'
        req.suffix = f'/lun/associate'
        req.body = json.dumps({
            'TYPE': AssociateObjectType.LUN,
            'ASSOCIATEOBJTYPE': AssociateObjectType.PROTECT_GROUP,
            'ASSOCIATEOBJID': protect_group_id
        })
        rsp = super()._send_request(req)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'query lun in lun group failed, error code: {error_code}, error desc: {error_desc}')
            return error_code, error_desc
        if rsp.get('data') and isinstance(rsp.get('data'), list):
            for data in rsp.get('data'):
                lun_info = LunInfo()
                lun_info.lun_id = data.get('ID')
                lun_info.name = data.get('NAME')
                lun_info.lun_type = data.get('TYPE')
                lun_info.parent_id = data.get('PARENTID')
                lun_info.health_status = data.get('HEALTHSTATUS')
                lun_info.running_status = data.get('RUNNINGSTATUS')
                lun_info.v_store_id = data.get('vstoreId')
                lun_infos.append(lun_info)
        return ReturnCode.SUCCESS, ''

    def query_lun_by_wwn(self, wwn, lun_info: LunInfo):
        req = HttpRequest()
        req.suffix = f'/lun?filter=WWN::{wwn}'
        req.method = 'GET'
        rsp = super()._send_request(req)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'query lun by wwn failed, error code: {error_code}, error desc: {error_desc}')
            return error_code, error_desc
        logger.debug(f"query_lun_by_wwn,{rsp}")
        if rsp.get('data') and isinstance(rsp.get('data'), list) and rsp.get('data')[0].get('WWN') == wwn:
            lun_info.lun_id = rsp.get('data')[0].get('ID')
            lun_info.lun_type = rsp.get('data')[0].get('TYPE')
            lun_info.wwn = wwn
            lun_info.running_status = rsp.get('data')[0].get('HEALTHSTATUS')
            lun_info.running_status = rsp.get('data')[0].get('RUNNINGSTATUS')
            lun_info.name = rsp.get('data')[0].get('NAME')
            lun_info.parent_id = rsp.get('data')[0].get('PARENTID')
            lun_info.v_store_id = rsp.get('data')[0].get('VSTOREID')
            lun_info.capacity = rsp.get('data')[0].get('CAPACITY')
        return error_code, error_desc

    def query_all_hyper_metro_pair(self, hyper_metro_pairs: List):
        req = HttpRequest()
        req.suffix = f'/HyperMetroPair'
        req.method = 'GET'
        rsp = super()._send_request(req)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'query_all_hyper_metro_pair failed, error code: {error_code}, error desc: {error_desc}')
            return error_code, error_desc
        if rsp.get('data') and isinstance(rsp.get('data'), list):
            for data in rsp.get('data'):
                hyper_metro_pair = HyperMetroPair()
                hyper_metro_pair.pair_id = data.get('ID')
                hyper_metro_pair.local_name = data.get('LOCALOBJNAME')
                hyper_metro_pair.local_access_state = data.get('LOCALHOSTACCESSSTATE')
                hyper_metro_pairs.append(hyper_metro_pair)
        return error_code, error_desc

    def query_lun_by_nguid(self, nguid, lun_info: LunInfo):
        req = HttpRequest()
        req.suffix = f'/lun?filter=NGUID::{nguid}'
        req.method = 'GET'
        rsp = super()._send_request(req)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'query lun by wwn ng_uid, error code: {error_code}, error desc: {error_desc}')
            return error_code, error_desc
        logger.debug(f"query_lun_by_nguid,{rsp}")
        if rsp.get('data') and isinstance(rsp.get('data'), list) and rsp.get('data')[0].get('NGUID') == nguid:
            lun_info.wwn = rsp.get('data')[0].get('WWN')
        return error_code, error_desc

    def add_lun_2_protect_group_by_id(self, lun_ids, protect_group_id):
        req = HttpRequest()
        req.method = 'POST'
        req.body = json.dumps({
            'lunIDs': lun_ids,
            'protectGroupId': protect_group_id
        })
        url = '/api/v2/task/protection/add_lun'
        rsp = super()._send_request(req, lock_session=True, url=url)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'add lun 2 lun group failed, error code: {error_code}, error desc: {error_desc}')
            return error_code, error_desc
        storage_task_info = StorageTaskInfo()
        storage_task_info.task_id = rsp.get('data', {}).get('taskId')
        if not storage_task_info.task_id:
            logger.error(
                f'add lun 2 group failed, task id not exist, ids: {lun_ids}, protect_group_id: {protect_group_id}')
            return ReturnCode.FAILED, 'task id not exist'
        return self.query_storage_task_status(storage_task_info)

    def delete_lun_from_protect_group(self, lun_ids, protect_group_id):
        req = HttpRequest()
        req.method = 'POST'
        req.body = json.dumps({
            'lunIDs': lun_ids,
            'protectGroupId': protect_group_id
        })
        url = '/api/v2/task/protection/remove_lun'
        rsp = super()._send_request(req, lock_session=True, url=url)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'delete lun 2 lun group failed, error code: {error_code}, error desc: {error_desc},'
                         f' lun_ids: {lun_ids}, protect_group_id: {protect_group_id}')
            return error_code, error_desc
        storage_task_info = StorageTaskInfo()
        storage_task_info.task_id = rsp.get('data', {}).get('taskId')
        if not storage_task_info.task_id:
            logger.error(
                f'del lun 2 group failed, task id not exist, ids: {lun_ids}, protect_group_id: {protect_group_id}')
            return ReturnCode.FAILED, 'task id not exist'
        return self.query_storage_task_status(storage_task_info)

    def query_protect_group(self, protect_group_name, protect_group_info: ProtectGroupInfo):
        req = HttpRequest()
        req.method = 'GET'
        url = f'/api/v2/protectgroup?filter=protectGroupName::{protect_group_name}'
        rsp = super()._send_request(req, url=url)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'query protect group failed, error code: {error_code}, error desc: {error_desc}')
            return error_code, error_desc
        if rsp.get('data') and isinstance(rsp.get('data'), list) and rsp.get('data')[0].get(
                'protectGroupName') == protect_group_name:
            protect_group_info.protect_group_id = rsp.get('data')[0].get('protectGroupId')
            protect_group_info.protect_group_name = rsp.get('data')[0].get('protectGroupName')
            protect_group_info.v_store_id = rsp.get('data')[0].get('vstoreId')
            protect_group_info.snapshot_group_num = rsp.get('data')[0].get('snapshotGroupNum')
            protect_group_info.lun_group_id = rsp.get('data')[0].get('lunGroupId')
            protect_group_info.lun_group_name = rsp.get('data')[0].get('lunGroupName')
            protect_group_info.lun_num = rsp.get('data')[0].get('lunNum')
        return error_code, error_desc

    def create_protect_group(self, name, protect_group_info: ProtectGroupInfo):
        req = HttpRequest()
        req.method = 'POST'
        req.body = json.dumps({
            'protectGroupName': name,
            'vstoreId': 0
        })
        url = '/api/v2/protectgroup'
        rsp = super()._send_request(req, lock_session=True, url=url)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'create protect group failed, error code: {error_code}, error desc: {error_desc}')
        else:
            logger.info(f'create protect group success, name: {name}')
            protect_group_info.protect_group_id = rsp.get('data').get('protectGroupId')
            protect_group_info.protect_group_name = rsp.get('data').get('protectGroupName')
            protect_group_info.v_store_id = rsp.get('data').get('vstoreId')
            protect_group_info.snapshot_group_num = rsp.get('data').get('snapshotGroupNum')
            protect_group_info.lun_group_id = rsp.get('data').get('lunGroupId')
            protect_group_info.lun_group_name = rsp.get('data').get('lunGroupName')
            protect_group_info.lun_num = rsp.get('data').get('lunNum')
            logger.info(f'protect_group_id:{protect_group_info.protect_group_id}')
        return error_code, error_desc

    def create_snapshot_consistency_group(self, name, protect_group_id,
                                          consistency_group: SnapshotConsistencyGroup):
        req = HttpRequest()
        req.method = 'POST'
        req.suffix = '/SNAPSHOT_CONSISTENCY_GROUP'
        req.body = json.dumps({
            'NAME': name,
            'PARENTID': protect_group_id,
            'vstoreId': 0
        })
        rsp = super()._send_request(req, lock_session=True)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'create snapshot consistency group failed, protect group id: {protect_group_id} '
                         f'error code: {error_code}, error desc: {error_desc}')
        else:
            consistency_group.group_id = rsp.get('data').get('ID')
            consistency_group.name = rsp.get('data').get('NAME')
            consistency_group.parent_id = rsp.get('data').get('PARENTID')
            consistency_group.parent_name = rsp.get('data').get('PARENTNAME')
            consistency_group.restore_speed = rsp.get('data').get('RESTORESPEED')
            consistency_group.v_store_id = rsp.get('data').get('vstoreId')
            logger.info(f'create snapshot consistency group success, group id: {protect_group_id}')
        return error_code, error_desc

    def delete_protect_group(self, group_id):
        req = HttpRequest()
        req.method = 'DELETE'
        url = f'/api/v2/protectgroup/{group_id}'
        rsp = self._send_request(req, lock_session=True, url=url)
        error_code, error_desc = self._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'delete protect group failed, error code: {error_code}, error desc: {error_desc}')
        else:
            logger.info(f'delete protect group success:{group_id}')
        return error_code, error_desc

    def delete_snapshot_consistency_group(self, group_id):
        req = HttpRequest()
        req.method = 'DELETE'
        req.suffix = f'/SNAPSHOT_CONSISTENCY_GROUP/{group_id}'
        rsp = super()._send_request(req, lock_session=True)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'delete snapshot consistency group failed, group id: {group_id} '
                         f'error code: {error_code}, error desc: {error_desc}')
        else:
            logger.info(f'delete snapshot consistency group success, id: {group_id}')
        return error_code, error_desc

    def query_snapshot_consistency_groups_by_protect_group_id(self, protect_group_id, consistency_groups: List):
        req = HttpRequest()
        req.method = 'GET'
        req.suffix = f'/SNAPSHOT_CONSISTENCY_GROUP/associate'
        req.body = json.dumps({
            'ASSOCIATEOBJID': protect_group_id,
            'ASSOCIATEOBJTYPE': AssociateObjectType.PROTECT_GROUP
        })
        rsp = super()._send_request(req)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'query snapshot consistency group failed, protect group id: {protect_group_id}, '
                         f'error code: {error_code}, error desc: {error_desc}')
            return error_code, error_desc
        if rsp.get('data') and isinstance(rsp.get('data'), list):
            for data in rsp.get('data'):
                group = SnapshotConsistencyGroup(
                    group_id=data.get("ID"), name=data.get('NAME'), parent_id=data.get('PARENTID'),
                    parent_name=data.get('PARENTNAME'), restore_speed=data.get('RESTORESPEED'),
                    v_store_id=data.get('VSTOREID'))
                consistency_groups.append(group)
        return ReturnCode.SUCCESS, ''

    def query_snapshot_consistency_group_by_id(self, consistency_group_id, snapshot_info: SnapshotInfo):
        req = HttpRequest()
        req.method = 'GET'
        req.suffix = f'/SNAPSHOT_CONSISTENCY_GROUP/{consistency_group_id}'
        rsp = super()._send_request(req)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'query snapshot consistency group by id failed, protect group id: {consistency_group_id}, '
                         f'error code: {error_code}, error desc: {error_desc}')
            return error_code, error_desc
        data = rsp.get('data')
        snapshot_info.snapshot_id = data.get('ID')
        snapshot_info.name = data.get('NAME')
        snapshot_info.snapshot_type = data.get('TYPE')
        snapshot_info.parent_id = data.get('PARENTID')
        snapshot_info.v_store_id = data.get('VSTOREID')
        return ReturnCode.SUCCESS, ''

    def query_snapshots_in_consistency_group(self, consistency_group_id, snapshots: List, lun_infos: List):
        req = HttpRequest()
        req.method = 'GET'
        req.suffix = f'/snapshot/associate'
        req.body = json.dumps({
            'ASSOCIATEOBJTYPE': AssociateObjectType.SNAPSHOT_CONSISTENCY_GROUP,
            'ASSOCIATEOBJID': consistency_group_id,
        })
        rsp = super()._send_request(req)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'query snapshot in consistency group failed, protect group id: {consistency_group_id}, '
                         f'error code: {error_code}, error desc: {error_desc}')
            return error_code, error_desc
        if rsp.get('data') and isinstance(rsp.get('data'), list):
            for data in rsp.get('data'):
                parent_id = data.get("PARENTID")
                parent_wwn = self.get_parent_wwn(lun_infos, parent_id)
                snapshot = SnapshotInfo(
                    snapshot_id=data.get("ID"), name=data.get('NAME'), snapshot_type=data.get('TYPE'),
                    parent_id=parent_id, v_store_id=data.get("VSTOREID"), wwn=data.get('WWN'),
                    parent_wwn=parent_wwn, snap_cons_group_id=consistency_group_id)
                snapshots.append(snapshot)
        return ReturnCode.SUCCESS, ''

    def get_parent_wwn(self, lun_infos, parent_id):
        parent_wwn = ''
        for lun_info in lun_infos:
            if parent_id == lun_info.lun_id:
                parent_wwn = lun_info.wwn
        if not parent_wwn:
            logger.warn("parent_wwn is not exist")
            parent_wwn = parent_id
        return parent_wwn

    def query_storage_task_status(self, task_info: StorageTaskInfo):
        req = HttpRequest()
        req.method = 'GET'
        url = f'/api/v2/task/{task_info.task_id}'
        rsp = self._send_request(req, lock_session=True, url=url)
        error_code, error_desc = self._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'Failed to query storage status, error code: {error_code}, error desc: {error_desc}')
            return error_code, error_desc
        task_info.task_status = rsp.get('data', {}).get('taskStatus', '')
        no_need_wait_status = ['success', 'fail']
        if task_info.task_status not in no_need_wait_status:
            time.sleep(5)
            logger.info(f'Wait for the task to be executed for 5 seconds.')
        return error_code, error_desc

    def query_device_system(self, device_system_info: DeviceSystemInfo):
        req = HttpRequest()
        req.suffix = f'/system/'
        req.method = 'GET'
        rsp = super()._send_request(req)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'query device system failed, error code: {error_code}, error desc: {error_desc}')
            return error_code, error_desc
        if rsp.get('data'):
            device_system_info.product_mode_string = rsp.get('data').get('productModeString')
        return error_code, error_desc

    def query_storage_pool_info(self, storage_pool_id, storage_pool_info: StoragePoolInfo):
        req = HttpRequest()
        req.suffix = f'/storagepool/{storage_pool_id}'
        req.method = 'GET'
        rsp = super()._send_request(req)
        error_code, error_desc = super()._check_response(rsp)
        if error_code != ReturnCode.SUCCESS:
            logger.error(f'query device system failed, error code: {error_code}, error desc: {error_desc}')
            return error_code, error_desc
        if rsp.get('data'):
            storage_pool_info.storage_pool_id = rsp.get('data').get('ID')
            storage_pool_info.name = rsp.get('data').get('NAME')
            storage_pool_info.storage_pool_used_capacity = rsp.get('data').get('USERFREECAPACITY')
            storage_pool_info.storage_pool_total_capacity = rsp.get('data').get('USERTOTALCAPACITY')
            storage_pool_info.storage_pool_capacity_usage_ratio = rsp.get('data').get('USERCONSUMEDCAPACITYPERCENTAGE')
        return ReturnCode.SUCCESS, ''
